#include <io.h>
#include <signal.h>


#include "../../tr_int.h"



/* Author: Ratish J. Punnoose, 2006
 * This file is part of TiROS, the Tickless Real-Time Operating System.
 * Copyright(c) 2006, 2007: Ratish J. Punnoose.
 * Copyright(c) 2006 Sandia Corporation. Under the terms of Contract
 * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
 * certain rights in this software. 
 * 
 * TiROS is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 or (at your option) any later version.
 *
 * TiROS is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with TiROS; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * As a special exception, if other files instantiate templates or use macros
 * or inline functions from this file, or you compile this file and link it
 * with other works to produce a work based on this file, this file does not
 * by itself cause the resulting work to be covered by the GNU General Public
 * License. However the source code for this file must still be made available
 * in accordance with section (3) of the GNU General Public License.
 *
 * This exception does not invalidate any other reasons why a work based on
 * this file might be covered by the GNU General Public License.
 *
 */



/** \name timer setup functions
 * @{ */

static trtime_t hal_os_time;
static trtime_t hal_next_alarm_time;

#define TxCLR  0x0004


/* This macro setup, allows us to use either Timer A or Timer B. Due
 * to the bizarre rules of C preprocessor stringification, we have 
 * to use a double macro call, as a single call does not prescan
 * (evaluate) the macro argument */ 


/* Eg. TIMERA0_VECTOR */
#define xMSP_TIMERxy_VECTOR(t, num) TIMER ## t ## num ## _VECTOR
#define MSP_TIMERxy_VECTOR(a,b) xMSP_TIMERxy_VECTOR(a,b)

/* Eg.  TACCR0 */
#define xMSP_TxCCRy(t, num) T ## t ## CCR ## num
#define MSP_TxCCRy(t, num)  xMSP_TxCCRy(t, num)

/* Eg.  TACTL */
#define xMSP_TxCTL(t) T ## t ## CTL
#define MSP_TxCTL(t)  xMSP_TxCTL(t)

/* Eg. TASSEL_ACLK */
#define xMSP_TxSSEL_ACLK(t) T ## t ## SSEL_ACLK
#define MSP_TxSSEL_ACLK(t)  xMSP_TxSSEL_ACLK(t)

/* Eg. TAIE */
#define xMSP_TxIE(t)  T ## t ## IE
#define MSP_TxIE(t)   xMSP_TxIE(t)

/* Eg. TACCTL0 */
#define xMSP_TxCCTLy(t,n)  T ## t ## CCTL ## n
#define MSP_TxCCTLy(t,n) xMSP_TxCCTLy(t,n)

/* Eg. TAR */
#define xMSP_TxR(t)  T ## t ## R
#define MSP_TxR(t)   xMSP_TxR(t)






/** Set the timer compare
 * @param t Timer value at which there should be an interrupt */
static inline void halint_set_timer_compare(uint16_t t)
{
	/* Set the register and enable timer */
	MSP_TxCCRy(TRPORT_MSP_OSTIMER, 0) = t;

	/* Reset TCCTLx register to SCS, CCIE, */
	MSP_TxCCTLy(TRPORT_MSP_OSTIMER,0) =  SCS | CCIE;
}


/** Disable the compare function */
static inline  void halint_disable_timer_compare(void) 
{ 
	/* Disable the compare register interrupt */
	MSP_TxCCTLy(TRPORT_MSP_OSTIMER,0) &= ~CCIE;
}

/** Get the timer value
 * @return Timer value */
static inline uint16_t  halint_get_timer_value(void) 
{ 
	return MSP_TxR(TRPORT_MSP_OSTIMER);
}



/** Update the timer subunits from the hal timer value.
 *  This also checks for counter rollover.
 * @return 1 if a timer rollover occurred, 0 otherwise */
static uint16_t halint_update_timer_subunits(void)
{
 	/* To avoid a possible race condition, we must also check to
	 * see if the counter has rolled over
	 *
	 *  The race condition can occur as follows:
	 *  os_time_get() is called.
	 *  interrupts are disabled.
	 *  counter overflows.
	 *
	 * halint_get_timer_value() is called
	 * It returns overflowed counter value.
	 * Since interrupts are disabled, the isr_counter_rollover has
	 * not yet been invoked.  So subunits is good, but units is not.
	 *  
	 */

	uint16_t timer_overflow;
	hal_os_time.subunits = halint_get_timer_value();

	timer_overflow = MSP_TxCTL(TRPORT_MSP_OSTIMER) & 0x0001;
	if (timer_overflow) {
		/* Clear the interrupt flag */
		MSP_TxCTL(TRPORT_MSP_OSTIMER) &=  ~0x0001;
		/* Increment the time units */
		hal_os_time.units += 1;
		/* Check subunits again, since the rollover may have happened
		 * after the last read, */
		hal_os_time.subunits = halint_get_timer_value();
	}
	return timer_overflow;
}

/** After a timer rollover, process alarms
 * @return 1 if a context switch is necessary, 0 otherwise  */
static int halint_re_alarm_after_rollover(void)
{
	/* If units match */
	if (hal_next_alarm_time.units == hal_os_time.units) {
		/* If subunits are within response time*/
		if (hal_next_alarm_time.subunits <    PORT_WAKEUP_RESPONSE_SUBUNITS) {
			if (osint_alarm_reached(&hal_os_time)) {
				return 1;
			}
		} else { /* Units match but subunits are later.  Set
			  * the alarm for later */
			halint_set_timer_compare(hal_next_alarm_time.subunits);
		}
	}
	return 0;
}





/** Set and enable the timer compare
 * @param subunits  Value for timer compare */
void  hal_alarm_set(trtime_t const *lt) 
{ 
	if (lt) {
		hal_next_alarm_time.units = lt->units;
		hal_next_alarm_time.subunits   = lt->subunits;
		if (hal_next_alarm_time.units == hal_os_time.units) {
			halint_set_timer_compare(hal_next_alarm_time.subunits);
		}


	} else {
		hal_next_alarm_time.units = ~0;
		hal_next_alarm_time.subunits   = ~0;
		halint_disable_timer_compare();
	}
	

}


/** Setup the timer
 *
 * This does the equivalent of:
 * T[A|B]CCR0 = 0xFFFF;
 * T[A|B]CTL  = T[A|B]SSEL_ACLK | T[A|B]IE  | MC_CONT | T[A|B]CLR
 * Setup Compare register using T[A|B]CCTL0
*/
static void halint_timer_setup(void)
{
	hal_os_time.units = 0;
	hal_os_time.subunits = 0;
	MSP_TxCCRy(TRPORT_MSP_OSTIMER, 0) = 0xFFFF;
	MSP_TxCCTLy(TRPORT_MSP_OSTIMER,0) = SCS;

	MSP_TxCTL(TRPORT_MSP_OSTIMER) = MSP_TxSSEL_ACLK(TRPORT_MSP_OSTIMER) |
		MSP_TxIE(TRPORT_MSP_OSTIMER) | MC_CONT | TxCLR;
}




















/** Flag determining if the task switcher should be invoked */
int halint_kernel_trap_flagged;
static inline void halint_setup_kernel_trap(void){ }


void hal_init(void)
{
	halint_timer_setup();
	/*lint -e{522} Next function does nothing */ 
	halint_setup_kernel_trap();
	
}

void hal_time_get(trtime_t *t)
{
	uint16_t timer_overflow;
	t->subunits = halint_get_timer_value();
	
	timer_overflow = MSP_TxCTL(TRPORT_MSP_OSTIMER) & 0x0001;
	if (timer_overflow) {
		/* Don't clear interrupt flag.  The interrupt routine
		 * will be triggered later */
		t->subunits = halint_get_timer_value();

		/* Due to overflow, add one to units */
		t->units =  hal_os_time.units + 1;
	} else {
		t->units = hal_os_time.units;
	}

}


int hal_time_set(trtime_t const *t)
{
	/* Stop the currently running timer */
	MSP_TxCTL(TRPORT_MSP_OSTIMER) = MSP_TxSSEL_ACLK(TRPORT_MSP_OSTIMER) |
		MSP_TxIE(TRPORT_MSP_OSTIMER) | MC_STOP;	
	/* Set the subunits value for the timer */
	MSP_TxR(TRPORT_MSP_OSTIMER) = t->subunits;

	/* Restart the timer */
	MSP_TxCTL(TRPORT_MSP_OSTIMER) = MSP_TxSSEL_ACLK(TRPORT_MSP_OSTIMER) |
		MSP_TxIE(TRPORT_MSP_OSTIMER) | MC_CONT;	
	hal_os_time.subunits = t->subunits;
	hal_os_time.units = t->units;
	/* What do we do about alarms ? 
	* We don't have to worry about alarms that have expired as a
	result of this time change.  The OS already takes care of
	that.  We also don't have to worry about alarms that are not
	present in this time supersec.  The time change doesn't affect
	them.  What we have to deal with is an alarm time that is in
	the current supersec, but later than t->subunits */
	halint_disable_timer_compare();
	if (hal_next_alarm_time.units == hal_os_time.units) {
		if (hal_next_alarm_time.units > hal_os_time.subunits)
			halint_set_timer_compare(hal_next_alarm_time.subunits);
		
	}
	

	/* This port can set time */
	return 1;
}

/** @} End timer functions */








/* The default SR only has the interrupt enable set. */
#define OS_DEF_SR	0x0008


/* Initialize the stack with this value */
#define MSP430_STACK_INIT_WORD           0x3C57

osstkptr_t hal_stk_init(osword_t *stk, osword_t stacksize, osfnptr_t pc,
		     osptr_t init_arg) 
{
	/* MSP430 stacks grows from high memory to low memory */
	osword_t *s = stk + stacksize-1;
	osword_t *stk_val;
#ifdef OLD_STUFF
	//osstkptr_t stk_init_start;
	//int i;
#endif

	*s-- = (osword_t)pc; 	/*  R0 = Program Counter */
	/* R1 = stack Pointer. */
	*s-- = OS_DEF_SR; /* R2 = Status Register.  */
	/* R3 =  Constant generator. */
	*s-- = 0x4444;  /* R4  */
	*s-- = 0x5555;  /* R5  */
	*s-- = 0x6666;  /* R6  */
	*s-- = 0x7777;  /* R7  */
	*s-- = 0x8888;  /* R8  */
	*s-- = 0x9999;  /* R9  */
	*s-- = 0xAAAA;  /* R10 */
	*s-- = 0xBBBB;  /* R11 */
	*s-- = 0xCCCC;  /* R12 */
	*s-- = 0xDDDD;  /* R13 */
	*s-- = 0xEEEE;  /* R14 */
	*s = (osword_t)init_arg; /* R15 contains the argument. */

#ifdef OLD_STUFF
	//stk_init_start = s-1;

	/* Initialize stack values */
/*
	for (i=0; i< stacksize - MSP430_INIT_STACK_OCCUPANCY; i++)
		*stk_init_start-- = MSP430_STACK_INIT_WORD;
		*/
#endif
	for (stk_val = (osword_t*)stk; stk_val < s;  stk_val++) 
		*stk_val = MSP430_STACK_INIT_WORD;


	return s;
}







#ifdef TIROSINT_DATA_DEBUG
/** Compute the program counter, from the stored context (stack
 * pointer)
 * This function is used for debugging.  Implement this by extracting
 * the program counter out of the stack pointer if possible.
 *
 * @param ctxt_ptr  The pointer to the context of the task
 * @return The program counter */
osptrword_t hal_pc_from_ctxt(osstkptr_t ctxt_ptr)
{
	/* The program counter register is 13 words up from the stack
	 * pointer */
	return (osptrword_t) *(ctxt_ptr + 13);
}


#ifdef TIROS_STK_CHECK

osword_t hal_stkusage_curr(osstkptr_t ctxt_ptr, osstkptr_t base_stkptr,
			   osword_t stksize)
{
	/* ctst_ptr s the stack pointer */
	
	/* The free stack is just the difference between this and the
	   allocated */

	return ( stksize - ((osword_t*)ctxt_ptr - (osword_t*)base_stkptr));
}

osword_t hal_stkusage_max(osstkptr_t ctxt_ptr, osstkptr_t base_stkptr,
			   osword_t stksize)
{
	osword_t rc;
	osword_t *base_stk = (osword_t*)base_stkptr;

	/* Measure free stack */
	for(rc = 0 ; rc < (stksize - MSP430_INIT_STACK_OCCUPANCY ); rc++) {
		if (*base_stk != MSP430_STACK_INIT_WORD)
			break;
		base_stk++;
	}

	/* Compute occupied stack */
	return (stksize - rc);
	
}

#endif /*TIROS_STK_CHECK */


#endif /* TIROSINT_DATA_DEBUG */








/* When this macro is called, the Program Counter is
 * already stored on the stack. 
 * Why-> This is the first invocation within hal_ctxt_switch().
 * When hal_ctxt_switch() is called, the PC is stored on the stack, so
 * that the program from continue from that point when the function ends.
 * The difference from an ISR invocation, is that the status register is not stored. */
#define HALINT_TASK_CTXT_SAVE() \
		"push r2	\n\t" 	\
		HALINT_CTXT_SAVE()

#define HALINT_CTXT_LOAD()	\
		"mov.w	r15, r1	\n\t"	\
		"pop	r15	\n\t"	\
		"pop	r14	\n\t"	\
		"pop	r13	\n\t"	\
		"pop	r12	\n\t"	\
		"pop	r11	\n\t"	\
		"pop	r10	\n\t"	\
		"pop	r9	\n\t"	\
		"pop	r8	\n\t"	\
		"pop	r7	\n\t"	\
		"pop	r6	\n\t"	\
		"pop	r5	\n\t"	\
		"pop	r4	\n\t"	\
		"reti		\n\t"	

/** Context switch from user level (non-ISR)
 *  This function is called from within an OS call, to switch the
 *  running process.  It also provides a return value. This is a
 *  pseudo return-value.  This function does not actually determine
 *  the value returned.  The return value is set by hal_retval_set
 *  which is often invoked when waking the process up from a blocked
 *  state. 
 * @return  Return value.  */
osstkptr_t hal_ctxt_switch(void)
{
 
	asm volatile( 
		HALINT_TASK_CTXT_SAVE()
		"call #osint_taskswitcher	\n\t"
		HALINT_CTXT_LOAD()
		      : /* no output registers */
		      : /* no input registers */
		);
	
	/* This is just to eliminate compiler warnings.  In theory,
	 * program execution flow never reaches this point because
	 * hal_ctxt_load performs a return from interrupt.  This
	 * tricks the calling process into looking at the return value
	 * in one of its registers.  This return value would have been
	 * set earlier by hal_retval_set. */
	return 0; 
}






























#ifndef USER_DEFINED_TIMER_TRAP
/** This is the ISR for counter rollover, on the MSP430 port, by
 * default it is the Timer A source as it is implemented in all
 * MSP430x1xx devices */



/*lint -esym(714, isr_counter_rollover) Not referenced */
/*lint -esym(765, isr_counter_rollover) Not static */
interrupt ( MSP_TIMERxy_VECTOR(TRPORT_MSP_OSTIMER,1)  ) isr_counter_rollover(void)  __attribute__ ( (naked));
interrupt ( MSP_TIMERxy_VECTOR(TRPORT_MSP_OSTIMER,1)  ) isr_counter_rollover(void)
{
	HALINT_ISR_ENTRY();

	/* This can also be done using the TAIV|TBIV register.
	 * However, accessing that register automatically resets the
	 * highest priority outstanding interrupt.   There may be
	 * higher priority interrupts than what this handler can
	 * support.  So since, we are only handling a subset of
	 * interrupts in this handler, it is best to not touch
	 * TAIV|TBIV.*/ 

	
	/* Update subunits and check for timer rollover */
	if (halint_update_timer_subunits()) {
		/* If timer has rolled over */
		if (halint_re_alarm_after_rollover() ) {
			OS_KERNEL_TRAP();
		}
	}
	

	HALINT_ISR_EXIT();
}

/*lint -esym(714, isr_compare_match) Not referenced */
/*lint -esym(765, isr_compare_match) Not static */

/** Interrupt function for a compare match */
interrupt ( MSP_TIMERxy_VECTOR(TRPORT_MSP_OSTIMER,0) ) isr_compare_match(void) __attribute__ ( (naked));
interrupt ( MSP_TIMERxy_VECTOR(TRPORT_MSP_OSTIMER,0) ) isr_compare_match(void) 
{
	HALINT_ISR_ENTRY();
	/* We are not enabling interrupts, and we have explicit
	 * knowledge of whether a context switch is required, so we
	 * don't use OS_ISR_BEGIN and OS_ISR_END. */
	halint_disable_timer_compare();

	/* Update subunits */
	(void)halint_update_timer_subunits();

	if (  osint_alarm_reached(&hal_os_time ) )
		OS_KERNEL_TRAP();
	HALINT_ISR_EXIT();
}









#endif /* USER_DEFINED_TIMER_TRAP */






